package com.link.base.base.alert.service;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.link.base.base.alert.dao.mybatis.mapper.AlertMapper;
import com.link.base.base.alert.model.Alert;
import com.link.base.base.alert.model.AlertParam;
import com.link.base.base.alert.model.AlertRecord;
import com.link.base.base.message.push.service.PushService;
import com.link.base.base.message.service.MessageService;
import com.link.base.base.position.service.PositionService;
import com.link.base.base.user.service.UserService;
import com.link.base.microinterface.sms.microservice.BaseMessageRecordClient;
import com.link.core.basic.dao.mapper.BasicMapper;
import com.link.core.basic.interceptor.SecurityInterceptor;
import com.link.core.basic.query.Filter;
import com.link.core.basic.query.Sorter;
import com.link.core.basic.service.BasicServiceException;
import com.link.core.basic.service.BasicServiceImpl;
import com.link.core.util.DateUtil;
import com.link.core.util.QueryParamUtil;
import com.link.core.util.StringUtils;
import com.xxl.job.core.log.XxlJobLogger;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;

/**
 * 预警模块-头
 *
 * @author 宁蓝昊
 * @date 2019/09/28 16:03
 */
@Service
public class AlertServiceImpl extends BasicServiceImpl<Alert> implements AlertService {

    @Resource
    private AlertMapper alertMapper;
    @Resource
    private AlertParamService alertParamService;
    @Resource
    private RestTemplate restTemplate;
    @Resource
    private MessageService messageService;
    @Resource
    private AlertRecordService alertRecordService;
    @Resource
    private PositionService positionService;
    @Resource
    private UserService userService;
    @Resource
    private BaseMessageRecordClient baseMessageRecordClient;
    @Resource
    private PushService pushService;

    @Override
    public BasicMapper<Alert> getBasicMapper() {
        return alertMapper;
    }

    @Override
    public void saveAlert(Alert entity) throws Exception {
        if (StringUtils.isBlank(entity.getAlertStatus())) {
            entity.setAlertStatus(AlertStatus.NEW);
        }
        if (StringUtils.isBlank(entity.getLastExecute())) {
            //默认上一次执行时间
            entity.setLastExecute(DateUtil.dateToStr(new Date(), DateUtil.DAY) + " 00:00:00");
        }
        String ruleLogicStr = entity.getRuleLogic();
        ruleLogicStr = replaceLogicStr(ruleLogicStr, new Date());
        // 查询参数
        JSONObject ruleLogic = null;
        try {
            ruleLogic = JSONObject.parseObject(ruleLogicStr);
        } catch (Exception e) {
            throw new BasicServiceException("解析预警判断规则逻辑失败");
        }
        if (ruleLogic == null) {
            throw new BasicServiceException("解析预警判断规则逻辑失败");
        }
        this.upsert(entity);
        Long alertId = entity.getId();
        List<AlertParam> alertParamList = entity.getAlertParamList();
        alertParamList = CollectionUtils.isEmpty(alertParamList) ? new ArrayList<>(0) : alertParamList;
        // 保存规则参数
        alertParamService.deleteByAlertId(alertId);
        for (AlertParam param : alertParamList) {
            param.setAlertId(alertId);
            alertParamService.insert(param);
        }
    }

    @Override
    public void executeAlert() throws Exception {
        // 获得当天开始时间
        Date currentDateTime = new Date();
        String currentDateStr = DateUtil.dateToStr(currentDateTime, DateUtil.DATE_TIME);
        // 查询符合条件的预警
        List<Alert> alerts = getAlertsInTime(currentDateStr);
        if (CollectionUtils.isEmpty(alerts)) {
            XxlJobLogger.log("当前时间【" + currentDateTime + "】无可执行预警任务");
            return;
        }
        for (Alert alert : alerts) {
            try {
                if (!checkExecuteTime(alert, currentDateTime)) {
                    continue;
                }
                // 查询需预警数据
                Map map = getAlertObjData(currentDateTime, alert);
                List<Map<String, Object>> rows = (List<Map<String, Object>>) map.get("rows");
                if (CollectionUtils.isEmpty(rows)) {
                    XxlJobLogger.log(alert.getId() + " 无可预警对象数据");
                    continue;
                }
                String receiveType = alert.getReceiveType();
                // 转化Object类型预警对象 rowid 到 String 类型方法
                Function<Object, String> mapObjectIdToStrFunc = businessId -> Optional.ofNullable(businessId)
                        .map(String::valueOf)
                        .orElseThrow(() -> new BasicServiceException("预警对象rowid不能为空"));
                if (AlertReceive.Employee.equals(receiveType)) {
                    // 发送消息
                    String infoType = alert.getInfoType();
                    for (Map<String, Object> data : rows) {
                        if (AlertInfo.SYS_INFO.equals(infoType)) {
                            //  系统消息的变量替换
                            String message = replaceMessageByParam(alert, data);
                            // 发送消息，增加对预警对象数据的row_id保存
                            String businessId = mapObjectIdToStrFunc.apply(data.get("id"));
                            sendPushNotice(Long.parseLong(businessId), alert.getId(), message, alert.getEmpId());
                        } else {
                            sendSms(alert, data, alert.getEmpId());
                        }
                    }
                    // 设置最后执行时间
                    updateLastExecute(new Date(), alert);
                } else if (AlertReceive.Conditional.equals(receiveType)) {
                    // 按条件则取被预警数据中的字段
                    for (Map<String, Object> data : rows) {
                        Object filedValueObj = data.get(alert.getObjectField());
                        if (filedValueObj == null) {
                            throw new BasicServiceException("【" + data.get("id").toString() + "】"
                                    + "【" + alert.getObjectField() + "】" + "字段为空");
                        }
                        String filedValue = mapObjectIdToStrFunc.apply(filedValueObj);
                        Long empId = getEmpId(Long.parseLong(filedValue), alert);
                        // 发送消息
                        String infoType = alert.getInfoType();
                        if (AlertInfo.SYS_INFO.equals(infoType)) {
                            //  系统消息的变量替换
                            String message = replaceMessageByParam(alert, data);
                            // 发送消息，增加对预警对象数据的row_id保存
                            String businessId = mapObjectIdToStrFunc.apply(data.get("id"));
                            sendPushNotice(Long.parseLong(businessId), alert.getId(), message, empId);
                        } else {
                            sendSms(alert, data, empId);
                        }
                    }
                    // 设置最后执行时间
                    updateLastExecute(new Date(), alert);
                } else {
                    throw new BasicServiceException("不正确消息接收人筛选类型");
                }
            } catch (Exception e) {
                e.printStackTrace();
                XxlJobLogger.log(alert.getId() + " 预警执行失败");
                XxlJobLogger.log(e.getMessage());
            }
        }
    }

    /**
     * 修改最后执行时间
     *
     * @author NingLanhao
     * @param lastExeTime 最后执行时间
     * @param alert       预警头
     * @return void
     * @date 2019-10-15 10:14
     */
    void updateLastExecute(Date lastExeTime, Alert alert) throws Exception {
        alert.setLastExecute(DateUtil.dateToStr(lastExeTime, DateUtil.DATE_TIME));
        update(alert);
    }

    /**
     * 获得empId，当预警的字段类型为员工时，直接返回原值，否则查询职位表获得userId
     *
     * @author NingLanhao
     * @param filedValue 原值
     * @param alert      预警头
     * @return java.lang.String
     * @date 2019-10-10 20:55
     */
    Long getEmpId(Long filedValue, Alert alert) throws Exception {
        return alert.getFieldType().equals(AlertField.EMPLOYEE)
                ? filedValue : positionService.queryById(filedValue).getUserId();
    }

    /**
     * 查询符合时间条件条件的预警
     *
     * @author NingLanhao
     * @param currentDateStr 当前日期
     * @return java.util.List
     * @date 2019-10-10 17:14
     */
    private List<Alert> getAlertsInTime(String currentDateStr) throws Exception {
        Alert alertFilter = (Alert) QueryParamUtil.addQpsFilter(Alert.class,
                new Filter("startDate", Filter.OP_LESS_EQUAL, currentDateStr),
                new Filter("alertStatus", AlertStatus.ACTIVE));
        // 筛选结束时间，可以为空
        alertFilter.setAttr1(currentDateStr);
        return this.queryByExamplePage(alertFilter);
    }

    /**
     * 1.保存预警记录，供短信接口取数据
     * 2.调用短信接口
     *
     * @author NingLanhao
     * @param alert 预警头
     * @param data  预警对象
     * @param empId 用户ID，用于获取发送短信手机号
     * @return void
     * @date 2019-10-09 14:14
     */
    private void sendSms(Alert alert, Map<String, Object> data, Long empId) throws Exception {

        // 获取参数
        List<AlertParam> alertParams = getAlertParams(alert.getId());
        // 插入预警记录
        String contactPhone = null;
        try {
            contactPhone = userService.queryById(empId).getContactPhone();
        } catch (NullPointerException e) {
            XxlJobLogger.log("预警对象【" + data.get("id").toString() + "】"
                    + "发送短信失败，不存在【" + empId + "】这个字段");
            throw new BasicServiceException("预警对象【" + data.get("id").toString() + "】"
                    + "发送短信失败，不存在【" + empId + "】这个字段");
        }
        AlertRecord alertRecord = new AlertRecord();
        alertRecord.setAlertId(alert.getId());
        alertRecord.setPhoneNum(contactPhone);
        // 保存预警对象数据的row_id，如拜访id、消费者id等
        String businessId = String.valueOf(data.get("id"));
        alertRecord.setBusinessId(Long.parseLong(businessId));
        // 预警记录赋值
        for (int i = 0; i < alertParams.size(); i++) {
            int index = i + 1;
            AlertParam param = alertParams.get(i);
            Object valueObj = data.get(param.getObjectField());
            String value = valueObj == null ? "" : valueObj.toString();
            Method setMethod = AlertRecord.class.getMethod("setAlertParam" + index, String.class);
            setMethod.invoke(alertRecord, value);
        }
        alertRecordService.insert(alertRecord);

        // 调用发送短信接口
        try {
            JSONObject messageQuery = new JSONObject();
            messageQuery.put("id", alert.getTemplateId());
            Map<String, Object> result = baseMessageRecordClient.queryById(messageQuery);
            XxlJobLogger.log("消息模板结果：" + JSONObject.toJSONString(result));
            Boolean succFlg = (Boolean) result.get("success");
            if (!succFlg) {
                throw new BasicServiceException("查询消息模板:" + result.get("result"));
            }
            Map<String, Object> template = (Map<String, Object>) result.get("result");
            String msgSignId = template.get("msgSignId").toString();
            String templateId = template.get("templateId").toString();
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("alertRecordId", alertRecord.getId());
            jsonObject.put("templateId", templateId);
            jsonObject.put("msgSignId", msgSignId);
            jsonObject.put("msgType", "Message");
            jsonObject.put("sendTarget", "AlertTarget");
            jsonObject.put("msgSource", "Alert");
            jsonObject.put("status", "Pending");
            jsonObject.put("sendMethod", "Immediately");
            Map<String, Object> messageRecordMap = baseMessageRecordClient.insert(jsonObject);
            XxlJobLogger.log("插入消息记录返回结果：" + JSONObject.toJSONString(messageRecordMap));
            Boolean messageRecordFlg = (Boolean) messageRecordMap.get("success");
            if (!messageRecordFlg) {
                throw new BasicServiceException("插入短信记录头:" + messageRecordMap.get("result"));
            }
            String newRowJsonStr = JSONObject.toJSONString(messageRecordMap.get("newRow"));
            JSONObject newRow = JSONObject.parseObject(newRowJsonStr);
            newRow.put("mobilePhone1Secret", contactPhone);
            Map<String, Object> sendMap = baseMessageRecordClient.sendManualMsg(newRow);
            XxlJobLogger.log("发送消息记录返回结果：" + JSONObject.toJSONString(sendMap));
        } catch (Exception e) {
            XxlJobLogger.log(e);
            throw new BasicServiceException(e.getMessage());
        }


    }

    /**
     * 根据提前配置的系统参数，替换系统消息文本
     *
     * @author NingLanhao
     * @param alert 预警头
     * @param map   预警对象
     * @date 2019-10-11 16:30
     */
    String replaceMessageByParam(Alert alert, Map<String, Object> map) throws Exception {
        List<AlertParam> alertParams = getAlertParams(alert.getId());
        String message = alert.getMessage();
        for (AlertParam params : alertParams) {
            String paramName = params.getParamName();
            String objectField = params.getObjectField();
            String replacement = map.get(objectField) == null ? "" : map.get(objectField).toString();
            message = message.replace("{" + paramName + "}", replacement);
        }
        return message;
    }

    /**
     * 获得预警参数
     *
     * @author NingLanhao
     * @param alertId 预警头Id
     * @return java.util.List
     * @date 2019-10-11 16:24
     */
    private List<AlertParam> getAlertParams(Long alertId) throws Exception {
        AlertParam alertIdParam = (AlertParam) QueryParamUtil.addQpsFilter(AlertParam.class,
                new Filter("alertId", String.valueOf(alertId)));
        Sorter sorter = new Sorter("seq", "asc");
        List sorters = new ArrayList();
        sorters.add(sorter);
        alertIdParam.getQps().setSorters(sorters);
        return alertParamService.queryByExamplePage(alertIdParam);
    }

    /**
     * 发送系统消息
     *
     * @author NingLanhao
     * @param businessId   预警对象数据的row_id，如消费者id、拜访id
     * @param alertId      头ID
     * @param alertMessage 预警消息
     * @param userId       发送对象
     * @return void
     * @date 2019-10-09 14:14
     */
    private void sendPushNotice(Long businessId, Long alertId, String alertMessage, Long userId) throws Exception {
        // 新增预警记录
        AlertRecord alertRecord = new AlertRecord();
        alertRecord.setAlertId(alertId);
        alertRecord.setEmpId(userId);
        alertRecord.setMessage(alertMessage);
        // 预警记录里保存预警对象数据的row_id
        alertRecord.setBusinessId(businessId);
        alertRecordService.insert(alertRecord);
        // 发送消息 预警的系统消息里objectId保存预警记录id
        pushService.sampleMessageForModule(alertMessage, userId, alertRecord.getId(), "Alert");
    }

    /**
     * 判断预警是否处于执行周期的那一天
     *
     * @author NingLanhao
     * @param alert           预警对象
     * @param currentDateTime 当前时间
     * @return java.lang.Boolean
     * @date 2019-10-08 16:33
     */
    private Boolean checkExecuteTime(Alert alert, Date currentDateTime) {
        // 预警周期
        Integer loopAmtInt = alert.getLoopAmt();
        String loopType = alert.getLoopType();
        // 开始时间 ,处理掉分秒
        String startDateStr = alert.getStartDate();
        Calendar startCa = Calendar.getInstance();
        startCa.setTime(DateUtil.stringToDate(startDateStr, DateUtil.DATE_TIME));
        startCa.set(Calendar.MINUTE, 0);
        startCa.set(Calendar.SECOND, 0);
        Date startDate = startCa.getTime();
        // 当前时间 ,处理掉分秒
        Calendar currentCa = Calendar.getInstance();
        currentCa.setTime(currentDateTime);
        currentCa.set(Calendar.MINUTE, 0);
        currentCa.set(Calendar.SECOND, 0);
        Date currentCaTime = currentCa.getTime();
        // 按照预警周期比较时间
        int hours = (int) ((currentCaTime.getTime() - startDate.getTime()) / (1000 * 3600));
        int days = hours / 24;
        int hoursRemain = hours % 24;
        int weeks = days / 7;
        int daysRemain = days % 7;
        if (AlertLoopType.MONTH.equals(loopType)) {
            int yearsBetween = currentCa.get(Calendar.YEAR) - startCa.get(Calendar.YEAR);
            int monthBetween = currentCa.get(Calendar.MONTH) - startCa.get(Calendar.MONTH);
            int currentMonthDay = currentCa.get(Calendar.DAY_OF_MONTH);
            int startMonthDay = startCa.get(Calendar.DAY_OF_MONTH);
            int dayOfMonthBetween = currentMonthDay - startMonthDay;
            // 判断月数差之前判断每月天数是否相等
            return hoursRemain == 0 && dayOfMonthBetween == 0 && (yearsBetween * 12 + monthBetween) % loopAmtInt == 0;
        } else if (AlertLoopType.WEEK.equals(loopType)) {
            return hoursRemain == 0 && daysRemain == 0 && weeks % loopAmtInt == 0;
        } else if (AlertLoopType.DAY.equals(loopType)) {
            return hoursRemain == 0 && days % loopAmtInt == 0;
        } else if (AlertLoopType.HOUR.equals(loopType)) {
            return hours % loopAmtInt == 0;
        }
        return false;
    }

    /**
     * 获得需预警数据
     *
     * @author NingLanhao
     * @param currentDateTime 当前时间
     * @param alert           预警对象
     * @return java.util.Map
     * @date 2019-10-09 10:05
     */
    private Map getAlertObjData(Date currentDateTime, Alert alert) {
        String ruleLogicStr = alert.getRuleLogic();
        ruleLogicStr = replaceLogicStr(ruleLogicStr, currentDateTime);
        XxlJobLogger.log(alert.getId() + " 预警对象查询参数为:" + ruleLogicStr);
        // 查询参数
        JSONObject ruleLogic = null;
        try {
            ruleLogic = JSONObject.parseObject(ruleLogicStr);
        } catch (JSONException e) {
            XxlJobLogger.log("【" + alert.getId() + "】解析预警判断规则逻辑失败，规则逻辑：");
            XxlJobLogger.log(ruleLogicStr);
            throw new BasicServiceException("【" + alert.getId() + "】解析预警判断规则逻辑失败");
        }
        // 查询接口URL,值列表维护
        String postUrl = alert.getObjectPath();
        HttpHeaders headers = new HttpHeaders();
        String innerToken = SecurityInterceptor.internal_token.get();
        headers.add(SecurityInterceptor.INNER_TOKEN, innerToken);
        HttpEntity entity = new HttpEntity<Object>(ruleLogic, headers);
        Map map = restTemplate.postForObject(postUrl, entity, Map.class);
        XxlJobLogger.log("请求接口【" + postUrl + "】返回参数：");
        XxlJobLogger.log(JSONObject.toJSONString(map));
        return map;
    }


    /**
     * 替换预警判断逻辑中的变量，变量支持加减
     *
     * @author NingLanhao
     * @param ruleLogicStr    逻辑判断字符串
     * @param currentDateTime 取当前定时器的时间
     * @return java.lang.String
     * @date 2019-10-08 15:12
     */
    String replaceLogicStr(String ruleLogicStr, Date currentDateTime) {
        int indexOfBegin = ruleLogicStr.indexOf(AlertVariable.BEGIN_FLAG);
        int indexOfEnd = ruleLogicStr.indexOf(AlertVariable.END_FLAG);
        if (indexOfBegin < 0 || indexOfEnd < 0) {
            // 无预警变量直接返回
            return ruleLogicStr;
        }
        // 变量字符串
        String variable = ruleLogicStr.substring(indexOfBegin, indexOfEnd + AlertVariable.END_FLAG.length());
        // 不带前后标志变量字符串
        String variableWithoutFlg = ruleLogicStr.substring(indexOfBegin + AlertVariable.BEGIN_FLAG.length(), indexOfEnd);
        int indexOfYear = variableWithoutFlg.indexOf(AlertVariable.CURRENT_YEAR);
        int indexOfDay = variableWithoutFlg.indexOf(AlertVariable.CURRENT_DAY);
        int indexOfBirthday = variableWithoutFlg.indexOf(AlertVariable.CURRENT_BIRTHDAY);
        int indexOfCurrentMonthBirthday = variableWithoutFlg.indexOf(AlertVariable.CURRENT_MONTH_BIRTHDAY);
        boolean hasYear = indexOfYear >= 0;
        boolean hasDay = indexOfDay >= 0;
        boolean hasBirthday = indexOfBirthday >= 0;
        boolean hasCurrentMonthBirthday = indexOfCurrentMonthBirthday >= 0;
        boolean onlyHasYear = hasYear && indexOfYear + AlertVariable.CURRENT_YEAR.length() == variableWithoutFlg.length();
        boolean onlyHasDay = hasDay && indexOfDay + AlertVariable.CURRENT_DAY.length() == variableWithoutFlg.length();
        boolean onlyHasBirthday = hasBirthday && indexOfBirthday + AlertVariable.CURRENT_BIRTHDAY.length() == variableWithoutFlg.length();
        boolean onlyHasCurrentMonthBirthday = hasCurrentMonthBirthday && indexOfCurrentMonthBirthday
                + AlertVariable.CURRENT_MONTH_BIRTHDAY.length() == variableWithoutFlg.length();
        String value = variableWithoutFlg;
        // 处理无加减符号情况
        if (onlyHasDay) {
            value = variableWithoutFlg.replace(AlertVariable.CURRENT_DAY,
                    DateUtil.dateToStr(currentDateTime, DateUtil.DAY));
        }
        if (onlyHasYear) {
            value = variableWithoutFlg.replace(AlertVariable.CURRENT_YEAR,
                    DateUtil.dateToStr(currentDateTime, "yyyy"));
        }
        if (onlyHasBirthday) {
            value = variableWithoutFlg.replace(AlertVariable.CURRENT_BIRTHDAY,
                    DateUtil.dateToStr(currentDateTime, "MM-dd"));
        }
        if (onlyHasCurrentMonthBirthday) {
            value = variableWithoutFlg.replace(AlertVariable.CURRENT_MONTH_BIRTHDAY,
                    DateUtil.dateToStr(currentDateTime, "MM"));
        }
        // 处理有加减符号情况
        boolean plus = value.indexOf("+") > -1;
        boolean minus = value.indexOf("-") > -1;
        // 约定变量在前，数字在后，切符号前后无括号,例如current_day+1
        String[] split = null;
        if (plus) {
            split = value.split("\\+");
        }
        if (minus) {
            split = value.split("\\-");
            // 减号取负数
            split[1] = "-" + split[1];
        }
        if (hasYear && !onlyHasYear) {
            String time = split[0].replace(AlertVariable.CURRENT_YEAR, DateUtil.dateToStr(currentDateTime, "yyyy"));
            Integer num = Integer.valueOf(split[1]);
            Date trueTime = DateUtil.addTimes(Calendar.YEAR, DateUtil.stringToDate(time, "yyyy"), num);
            value = DateUtil.dateToStr(trueTime, "yyyy");
        }
        if (hasDay && !onlyHasDay) {
            String time = split[0].replace(AlertVariable.CURRENT_DAY, DateUtil.dateToStr(currentDateTime, DateUtil.DAY));
            Integer num = Integer.valueOf(split[1]);
            Date trueTime = DateUtil.addTimes(Calendar.DAY_OF_YEAR, DateUtil.stringToDate(time, DateUtil.DAY), num);
            value = DateUtil.dateToStr(trueTime, DateUtil.DAY);
        }
        if (hasBirthday && !onlyHasBirthday) {
            String time = split[0].replace(AlertVariable.CURRENT_BIRTHDAY, DateUtil.dateToStr(currentDateTime, "MM-dd"));
            Integer num = Integer.valueOf(split[1]);
            Date trueTime = DateUtil.addTimes(Calendar.DAY_OF_MONTH, DateUtil.stringToDate(time, "MM-dd"), num);
            value = DateUtil.dateToStr(trueTime, "MM-dd");
        }
        if (hasCurrentMonthBirthday && !onlyHasCurrentMonthBirthday) {
            String time = split[0].replace(AlertVariable.CURRENT_MONTH_BIRTHDAY, DateUtil.dateToStr(currentDateTime, "MM-dd"));
            Integer num = Integer.valueOf(split[1]);
            Date trueTime = DateUtil.addTimes(Calendar.MONTH, DateUtil.stringToDate(time, "MM-dd"), num);
            value = DateUtil.dateToStr(trueTime, "MM");
        }
        return ruleLogicStr.replace(variable, value);
    }

}

